package com.rtsapp.server.benchmark.cfg;

import com.rtsapp.server.benchmark.ITestCase;
import com.rtsapp.server.benchmark.ITestCaseConfig;
import com.rtsapp.server.benchmark.ITestCaseIterator;
import com.rtsapp.server.benchmark.cases.asst.AssertCase;
import com.rtsapp.server.benchmark.cases.attr.SetAttrCase;
import com.rtsapp.server.benchmark.cases.cmd.CommandCase;
import com.rtsapp.server.benchmark.cases.attr.RemoveAttrCase;
import com.rtsapp.server.benchmark.cases.asst.AssertInfo;
import com.rtsapp.server.benchmark.cases.asst.AssertUtils;
import com.rtsapp.server.benchmark.cases.prop.SetPropCase;
import com.rtsapp.server.benchmark.exp.ExpUtils;
import com.rtsapp.server.network.protocol.command.ICommand;
import com.rtsapp.server.utils.StringUtils;
import junit.framework.Assert;
import com.rtsapp.server.logger.Logger;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.*;


/**
 * Created by admin on 15-9-15.
 * 测试用的xml配置文件
 */
public class TestCaseXmlConfig implements ITestCaseConfig {

    private static Logger log =com.rtsapp.server.logger.LoggerFactory.getLogger(TestCaseXmlConfig.class);

    private static final String EL_COMMAND = "command";
    private static final String EL_REMOVE = "remove";
    private static final String EL_SET = "set";
    private static final String EL_SETPROP = "setProp";
    private static final String EL_ASSERT = "assert.";
    private static final String EL_TESTCASE = "testcase";

    private static final String EL_GROUP = "group";

    //目前不支持嵌套的foreach, foreach中不能有group, foreach等多层结构
    private static final String EL_FOREACH = "foreach";
    //for也不支持嵌套
    private static final String EL_FOR = "for";


    //预计测试总耗时毫秒
    private long executeTimeMills=0;
    //按组名索引的一组测试用例信息
    private Map<String, GroupInfo> groupInfos = new HashMap<String, GroupInfo>();
    //所有的测试用例
    private List<Element> allTestCaseElements = new ArrayList<>();

    private XmlTestCaseCreator testCreator;

    public TestCaseXmlConfig( String caseFile ){
        this( caseFile, false );
    }

    /**
     * @param caseFile 配置文件
     * @param ignoreDelay 是否忽略延迟
     */
    public TestCaseXmlConfig(String caseFile, boolean ignoreDelay ) {

        log.info("解析测试用例:" + caseFile);

        File file = new File(caseFile);

        //1. 解析xml
        Document dom = parseXml(file);

        if (dom != null) {
            //2. 遍历一遍, 得出测试用例Map
            visite(file, dom.getRootElement());

            //3. 包名
            String commandPackage = dom.getRootElement().attributeValue("commandPackage");
            long timeunit = Long.parseLong(  dom.getRootElement().attributeValue("timeunit", "1" ) );

            if( ignoreDelay ){
                timeunit = 0;
            }

            testCreator = new XmlTestCaseCreator(commandPackage, timeunit );
        }

        //2. 检测测试用例
    }

    /**
     * TODO  validateConfig 没有完成, 不能使用
     *
     */
    private void validateConfig(){
        Map<String, Object> context = new HashMap<>();
        ITestCaseIterator it =  this.newTestCaseIterator(context);
        while( it.hasNext() ){
            ITestCase testCase = it.next();
            Assert.assertNotNull( testCase );
        }
    }

    /**
     * 深度遍历测试用例文档
     *
     * @param xmlFile 当前正在解析的xml文件
     * @param root    当前要解析的元素
     */
    private void visite(File xmlFile, Element root) {

        if ( isImport(root) ) {
            visiteImportFile(xmlFile, root);
        } else if (isTestCase(root)) {
            appendTestCase(root);
        } else if( isForeach( root ) ){
            appendTestCase( root );
        } else if( isFor( root ) ){
            appendTestCase( root );
        } else {

            boolean isGroup = isGroup(root);
            if (isGroup) {
                beginGroup(root);
            }

            List<Element> childrens = root.elements();
            for (Element child : childrens) {
                visite(xmlFile, child);
            }

            if (isGroup) {
                endGroup(root);
            }
        }
    }

    /**
     * 记录一个组的开始信息
     *
     * @param root
     * @return
     */
    private void beginGroup(Element root) {
        String groupName = root.attributeValue("name");
        GroupInfo info = new GroupInfo();
        info.setName(groupName);
        info.setStartIndex(this.allTestCaseElements.size());
        this.groupInfos.put(groupName, info);
    }

    /**
     * 结束一个组的信息
     *
     * @param root
     */
    private void endGroup(Element root) {
        String groupName = root.attributeValue("name");
        GroupInfo info = this.groupInfos.get(groupName);
        info.setEndIndex(this.allTestCaseElements.size());
    }

    /**
     * 是否是一个测试用例组
     *
     * @param root
     * @return
     */
    private boolean isGroup(Element root) {

        if (root.getName().equalsIgnoreCase("group")) {
            String groupName = root.attributeValue("name");
            if (!StringUtils.isEmpty(groupName)) {
                if (!this.groupInfos.containsKey(groupName)) {
                    return true;
                } else {
                    log.error("group name 重复, 略过: name=" + groupName);
                }
            } else {
                log.error("group name is null" + root);
            }
        }

        return false;
    }


    /**
     * 是否是Foreach标签
     * @param root
     * @return
     */
    private static boolean isForeach(Element root) {
        String name = root.getName();

        return name.equalsIgnoreCase( EL_FOREACH );
    }

    /**
     * 是否是For标签
     * @param root
     * @return
     */
    private static boolean isFor(Element root) {
        String name = root.getName();

        return name.equalsIgnoreCase( EL_FOR );
    }

    /**
     * 是否是一个测试用例
     *
     * @param root
     * @return
     */
    private static boolean isTestCase(Element root) {
        String name = root.getName();

        return name.equalsIgnoreCase( EL_COMMAND ) ||
                name.equalsIgnoreCase( EL_REMOVE ) ||
                name.equalsIgnoreCase( EL_SET ) ||
                name.equalsIgnoreCase( EL_SETPROP ) ||
                name.equalsIgnoreCase( EL_TESTCASE ) ||
                name.startsWith(EL_ASSERT);
    }


    /**
     * 追加一个测试用例到组里面
     *
     * @param root
     */
    private void appendTestCase(Element root) {
        long delayTime = Long.parseLong(root.attributeValue("delay", "0"));
        executeTimeMills += delayTime;

        this.allTestCaseElements.add(root);
    }


    /**
     * 是否import文件
     *
     * @param root
     * @return
     */
    private boolean isImport(Element root) {
        return root.getName().equalsIgnoreCase("import");
    }

    /**
     * visite一个import文件
     *
     * @param parentFile 包含import的文件
     * @param root
     */
    private void visiteImportFile(File parentFile, Element root) {

        String fileName = root.attributeValue("file", null);
        File file;
        if (!fileName.startsWith("/")) {
            file = new File(parentFile.getParentFile(), fileName);
        } else {
            file = new File(fileName);
        }

        Document dom = parseXml(file);
        if (dom == null) {
            log.error("import file error, file=" + file);
        } else {
            visite(file, dom.getRootElement());
        }

    }


    /**
     * 解析xml，获取dom
     *
     * @param file 要解析的File
     * @return
     */
    private Document parseXml(File file) {

        String filePath = file.getAbsolutePath();

        log.info("parseXml filePath=" + filePath);

        if (!file.exists()) {
            log.error("parseXml file not exist, filePath=" + filePath);
            return null;
        }
        try {
            Document dom = new SAXReader().read(file);
            return dom;
        } catch (DocumentException e) {
            log.error("parseXml error, filePath=" + filePath, e);
            return null;
        }
    }

    @Override
    public long getExecuteTimeMills(){
        return executeTimeMills;
    }

    @Override
    public int getCaseCount(){
        return this.allTestCaseElements.size();
    }

    /**
     * 获取一个测试用例的迭代器
     *
     * @param ctx
     * @return
     */
    @Override
    public ITestCaseIterator newTestCaseIterator(Map ctx) {
        return new XmlTestCaseIterator(testCreator, ctx, this.allTestCaseElements, 0, this.allTestCaseElements.size());
    }


    /**
     * 根据测试用例的组名获取一个测试用例迭代器
     *
     * @param ctx
     * @param groupName
     * @return
     */
    @Override
    public ITestCaseIterator newTestCaseIterator(Map ctx, String groupName) {

        GroupInfo groupInfo = this.groupInfos.get(groupName);
        if (groupInfo == null) {
            return null;
        }

        return new XmlTestCaseIterator(testCreator, ctx, this.allTestCaseElements, groupInfo.getStartIndex(), groupInfo.getEndIndex());
    }

    /**
     * 默认case节点为叶子节点
     * 其他节点
     */
    private static class XmlTestCaseIterator implements ITestCaseIterator {

        private final XmlTestCaseCreator creator;
        private final Map ctx;
        private final List<Element> caseElements;
        private final int startIndex;
        private final int endIndex;
        private int index;

        //foreach支持的属性
        private boolean inForeach = false ;
        private Iterator forearchIt;
        private String itVar;

        //for支持的属性
        private boolean inFor = false;
        private int begin;
        private int end;
        private int step;
        private int curStep;
        private String forVar;

        //foreach,for共用的循环内部测试用例
        private List<Element> nestElements;
        private int nestIndex;


        public XmlTestCaseIterator(XmlTestCaseCreator creator, Map ctx, List<Element> caseElements, int startIndex, int endIndex) {
            this.creator = creator;
            this.ctx = ctx;
            this.caseElements = caseElements;
            this.startIndex = startIndex;
            this.endIndex = endIndex;
            this.index = startIndex;
        }

        @Override
        public boolean hasNext() {
            return index < endIndex;
        }

        @Override
        public ITestCase next() {

            if( inFor ){

                Element element = nestElements.get( nestIndex );
                ITestCase testCase = creator.createTestCase( ctx, element );

                nestIndex++;
                if( nestIndex >= nestElements.size() ){
                    nestIndex = 0;

                    //如果还有循环
                    if( Math.abs( curStep + step - begin ) <= Math.abs( end - begin ) ){
                        curStep = curStep+step;
                        ctx.put( forVar, curStep );
                    }else{
                        ctx.remove( forVar );
                        inFor = false;

                        nestElements = null;
                        nestIndex = 0;

                        //foreach本身也是一个caseElement, 完成后index++
                        this.index++;
                    }
                }

                return testCase;

            } else if( inForeach ){

                Element element = nestElements.get( nestIndex );
                ITestCase testCase = creator.createTestCase( ctx, element );

                nestIndex++;
                if( nestIndex >= nestElements.size() ){
                    nestIndex = 0;

                    if( forearchIt.hasNext() ){

                        Object var =  forearchIt.next();
                        ctx.put( itVar, var );

                    }else{
                        ctx.remove( itVar );
                        inForeach = false;
                        forearchIt = null;

                        nestElements = null;
                        nestIndex = 0;

                        //foreach本身也是一个caseElement, 完成后index++
                        this.index++;
                    }
                }

                return testCase;

            }else{

                Element element = caseElements.get(this.index);

                if( element.getName().equalsIgnoreCase("foreach") ){

                    String itemsExp = element.attributeValue("items", null );
                    itVar = element.attributeValue("var", null);
                    if( itemsExp == null || itVar == null ){
                        throw new RuntimeException( "foreach 标签配置错误, items, var 都不能为空" );
                    }

                    Object collectionObj = ExpUtils.getValue(ctx, itemsExp);
                    if( collectionObj == null ||  !( collectionObj instanceof  Collection ) ){
                        throw new RuntimeException( "items 不能为null,且必须是Collection的子类" );
                    }

                    Collection collection =  (Collection)collectionObj;
                    nestElements =  parseChildTestCase( element );

                    if( collection.size() < 1 || nestElements.size() < 1 ){
                        this.index++;
                        return next();
                    }else{
                        forearchIt = collection.iterator();
                        inForeach = true;
                        nestIndex = 0;

                        Object var =  forearchIt.next();
                        ctx.put( itVar, var );
                        return next();
                    }
                }else if( element.getName().equalsIgnoreCase( "for" ) ){
                    String beginStr = element.attributeValue( "begin", null );
                    String endStr = element.attributeValue( "end", null );
                    String steptr = element.attributeValue( "step", "1" );
                    forVar = element.attributeValue( "var", "var");
                    if( beginStr == null || endStr == null || steptr == null || forVar == null ){
                        this.index++;
                        return next();
                    }

                    try {
                        begin = (Integer)ExpUtils.getValue(ctx, beginStr);
                        end =   (Integer)ExpUtils.getValue(ctx, endStr);
                        step = Integer.parseInt(steptr);
                    }catch( Throwable ex ){
                        this.index++;
                        return next();
                    }

                    nestElements = parseChildTestCase( element );
                    if( nestElements == null || nestElements.size() < 1 ){
                        this.index++;
                        return next();
                    }else{
                        inFor = true;
                        curStep = begin;
                        nestIndex = 0;

                        ctx.put( forVar, curStep );

                        return next();
                    }

                }
                else{
                    this.index++;
                    return creator.createTestCase(ctx, element);
                }

            }

        }


        private List<Element> parseChildTestCase( Element element ){

            List<Element> childElements = new ArrayList<>( );
            List<Element> childrens = element.elements();
            for ( Element child : childrens ) {
                if( isTestCase( child ) ){
                    childElements.add(child);
                }
            }

            return childElements;
        }

    }


    /**
     * 一个组的信息
     */
    private static class GroupInfo {
        private String name;
        //从这个位置开始，包含该位置的元素
        private int startIndex;
        //不包含该位置的元素
        private int endIndex;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getStartIndex() {
            return startIndex;
        }

        public void setStartIndex(int startIndex) {
            this.startIndex = startIndex;
        }

        public int getEndIndex() {
            return endIndex;
        }

        public void setEndIndex(int endIndex) {
            this.endIndex = endIndex;
        }
    }


    /**
     * 测试用例实例创建
     */
    private static class XmlTestCaseCreator {

        private final String commandPkg;
        private final long timeunit ;

        public XmlTestCaseCreator(String commandPkg, long timeunit ) {
            this.commandPkg = commandPkg + ".";
            this.timeunit = timeunit;
        }

        public ITestCase createTestCase( Map ctx, Element element) {

            try {
                String name = element.getName();
                if (name.equalsIgnoreCase( EL_COMMAND )) {
                    return createCommandCase(ctx, element);
                } else if (name.equalsIgnoreCase( EL_REMOVE )) {
                    return createRemoveAttrCase(ctx, element);
                } else if( name.equalsIgnoreCase( EL_SET )){
                    return createSetAttrCase(ctx, element);
                } else if( name.equalsIgnoreCase(EL_SETPROP)){
                    return createSetPropCase(ctx, element);
                } else if (name.startsWith( EL_ASSERT )) {
                    return createAssertCase(ctx, element);
                }else if( name.equalsIgnoreCase( EL_TESTCASE ) ){
                    return createCustomerTestCase(ctx, element);
                }else{
                    throw new RuntimeException( "测试用例配置错误, 无法创建对应的测试用例" );
                }
            } catch (Throwable ex) {
                log.error("createTestCase error", ex);
                throw new RuntimeException( "测试用例创建失败",  ex );
            }

        }

        private ITestCase createCustomerTestCase( Map ctx, Element element ){

            String className =   element.attributeValue( "class" );

            try {
                Class clz =  Class.forName(className);

                //创建自定义TestCase
                Constructor c =  clz.getConstructor(Map.class);
                ITestCase testCase = (ITestCase)c.newInstance( ctx );

                //设置所有的属性值
                List<Element> propElements = element.elements();
                for (Element propEle : propElements) {
                    String propertyName = propEle.getName();
                    String valueExp = propEle.attributeValue("value");
                    Object value = ExpUtils.getValue(ctx, valueExp);
                    ExpUtils.setValue( testCase, propertyName, value);
                }

                return testCase;

            } catch (ClassNotFoundException e) {
                log.error( "createCustomerTestCase exception", e );
            } catch (InvocationTargetException e) {
                log.error( "createCustomerTestCase exception", e );
            } catch (NoSuchMethodException e) {
                log.error( "createCustomerTestCase exception", e );
            } catch (InstantiationException e) {
                log.error( "createCustomerTestCase exception", e );
            } catch (IllegalAccessException e) {
                log.error( "createCustomerTestCase exception", e );
            }

            return null;
        }


        private CommandCase createCommandCase(Map ctx, Element element) throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException {

            String commandName = element.attributeValue("name");
            String className = element.attributeValue( "class", null );

            long delay = Long.parseLong(element.attributeValue("delay", "0"));
            delay = delay * timeunit;

            //request
            ICommand request = (ICommand) Class.forName(this.commandPkg + commandName + "Request").newInstance();
            String requestAttrKey = null;

            Element reqElement = element.element("request");
            if( reqElement != null ) {
                List<Element> propElements = reqElement.elements();
                for (Element propEle : propElements) {
                    parseProperty( ctx,  request, propEle );
                }
                requestAttrKey = reqElement.attributeValue("attr", null);
            }

            //response
            ICommand response = (ICommand) Class.forName(this.commandPkg + commandName + "Response").newInstance();
            String responseAttrKey = null;
            Element responseElement = element.element("response");
            if (responseElement != null) {
                responseAttrKey = responseElement.attributeValue("attr", null);
            }

            //create testCase
            CommandCase testCase = null;
            if( className == null ) {
                testCase = new CommandCase(ctx, delay, request, response, requestAttrKey, responseAttrKey);
            }else{
                Constructor constructor =  Class.forName( className ).getConstructor( Map.class, long.class, ICommand.class, ICommand.class, String.class, String.class );
                testCase = (CommandCase)constructor.newInstance( ctx, delay, request, response, requestAttrKey, responseAttrKey );
            }

            //response assert, 可以没有response节点
            if (responseElement != null) {
                List<Element> assertElements = responseElement.elements();
                for (Element assertEle : assertElements) {

                    AssertInfo assertInfo = parseAssertInfo(assertEle, false );

                    if (assertInfo != null) {
                        testCase.addAssertInfo(assertInfo);
                    }
                }
            }


            return testCase;
        }

        /**
         * 解析配置文件中，对象属性值的配置
         * @param ctx 表达式取值的上下文
         * @param currentObj 要进行属性设置的对象
         * @param propEle 要解析的xml元素
         */
        private void parseProperty( Map<String,Object> ctx,  Object currentObj, Element propEle ) throws IllegalAccessException, InstantiationException, ClassNotFoundException {


            try {


                // 如果当前要做操的对象是List
//                String currentObjectClass = currentObj.getClass().getSimpleName();
//                currentObjectClass.equals( "List" ) || currentObjectClass.equals( "ArrayList" )
                if( currentObj instanceof List  ){

                    List curList = (List)currentObj;

                    // 在List中，属性名字代表List中元素的类型,如(<int>1</int> <String>'a'<String>这样)
                    String propertyType = propEle.getName();
                    switch ( propertyType ){
                        case "int":
                        case "Integer":
                        case "byte":
                        case "Byte":
                        case "boolean":
                        case "Boolean":
                        case "float":
                        case "Float":
                        case "long":
                        case "Long":
                        case "String": {

                            String valueExp = propEle.attributeValue("value");
                            if( valueExp == null ){
                                return;
                            }


                            Object value = ExpUtils.getValue(ctx, valueExp);
                            switch ( propertyType ) {
                                case "int":
                                case "Integer":{
                                    curList.add( Integer.valueOf( value.toString() ) );
                                    break;
                                }
                                case "byte":
                                case "Byte":{
                                    curList.add( Byte.valueOf( value.toString() ) );
                                    break;
                                }
                                case "boolean":
                                case "Boolean":{
                                    curList.add( Boolean.valueOf( value.toString() ) );
                                    break;
                                }
                                case "float":
                                case "Float":{
                                    curList.add( Float.valueOf( value.toString() ) );
                                    break;
                                }
                                case "long":
                                case "Long":{
                                    curList.add( Long.valueOf( value.toString() ) );
                                    break;
                                }
                                case "String": {
                                    curList.add(  value.toString()  );
                                    break;
                                }
                            }

                            break;
                        }
                        case "List":
                        case "list":{
                            throw new NotImplementedException( );
                        }
                        default:{

                            // TODO 创建对象方式需要优化， 这里List中的元素只能是.model包中对象, 有没有什么办法能够找到List中元素的类型，然后直接反射出对象了？
                            Class<?> propClass =  Class.forName( commandPkg + ".model." + propertyType  );
                            Object obj =  propClass.newInstance( );
                            curList.add( obj );

                            List<Element> childElements = propEle.elements();
                            for (Element childEle : childElements) {
                                parseProperty( ctx, obj, childEle );
                            }
                            break;
                        }
                    }

                }else{ // 当前操作对象是非List的对象

                    String propertyName = propEle.getName();

                    Field field = currentObj.getClass().getField(propertyName);
                    String filedClassName = field.getType().getSimpleName();

                    switch ( filedClassName ){
                        case "int":
                        case "Integer":
                        case "byte":
                        case "Byte":
                        case "boolean":
                        case "Boolean":
                        case "float":
                        case "Float":
                        case "long":
                        case "Long":
                        case "String": {
                            //基本类型直接赋值
                            String valueExp = propEle.attributeValue("value");
                            Object value = ExpUtils.getValue(ctx, valueExp);
                            ExpUtils.setValue( currentObj, propertyName, value);
                            break;

                        }
                        case "List":
                        case "ArrayList":
                        default: { // List, ArrayList, Object类型都是先保证对象存在，然后遍历子节点进行处理

                            Object objProp =  field.get(currentObj);

                            // 如果list属性不存在，创建赋值
                            if( objProp == null ){
                                objProp = ( field.getClass().newInstance() );
                                field.set( currentObj, objProp );
                            }

                            //循环设置list元素
                            List<Element> childElements = propEle.elements();
                            for (Element childEle : childElements) {
                                parseProperty(ctx, objProp, childEle);
                            }

                            break;
                        }
                    }

                }

            } catch (NoSuchFieldException e) {
                log.error(  e );
            }

        }


        private RemoveAttrCase createRemoveAttrCase(Map ctx, Element element) {
            String attrKey = element.attributeValue("attr", null);
            if (attrKey == null) {
                return null;
            }

            return new RemoveAttrCase(ctx, attrKey);
        }

        private SetAttrCase createSetAttrCase(Map ctx, Element element) {
            String attrKey = element.attributeValue("attr", null);
            String objExp =  element.attributeValue("value", null);

            if (attrKey == null || objExp == null ) {
                return null;
            }

            Object obj = ExpUtils.getValue( ctx, objExp );
            return new SetAttrCase(ctx, attrKey, obj );
        }

        private SetPropCase createSetPropCase(Map ctx, Element element) {

            if( element.attributeCount() < 1 ){
                return null;
            }

            Attribute attr =  (Attribute)element.attributes().get( 0 );
            String propExp =  attr.getName();
            String valueExp = attr.getValue();

            if ( propExp == null || valueExp == null ) {
                return null;
            }

            propExp = ExpUtils.tranforExpInXmlAttrName(  propExp, true );

            return new SetPropCase(ctx, propExp, valueExp );
        }


        private AssertCase createAssertCase(Map ctx, Element element) {

            AssertInfo assertInfo = parseAssertInfo(element, true );
            if (assertInfo == null) {
                return null;
            }

            return new AssertCase(ctx, assertInfo);
        }


        private AssertInfo parseAssertInfo(Element element, boolean isInContext) {

            AssertInfo assertInfo = AssertUtils.createAssertInfo(element.getName());
            if (assertInfo == null) {
                return null;
            }

            Iterator it = element.attributeIterator();
            while (it.hasNext()) {

                Attribute attr = (Attribute) it.next();

                String exp = attr.getName();
                exp = ExpUtils.tranforExpInXmlAttrName(  exp, isInContext );

                String expectValueExp = attr.getValue();

                assertInfo.addAssert( exp,  expectValueExp );
            }

            return assertInfo;
        }

    }

}
